﻿/* SOWY KeywordProcessor class
 * Author:
 * Group: f10-2s
 * Version 0.01
 * This class is to retrieve keywords and process them, telling the operation class what type of task it is:
 * floating, deadline or duration tasks
 * The main part of this class is to figure out what the user wants and when the user wants it from his inputs
 * Specially important since we doing flexi command
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SOWY.common;

namespace SOWY
{
    class KeywordProcessing
    {
        DataProcessing dp;
        private string dateTypeKeywords;
        private string task;
        private string date;
        private string taskStatus;

        public KeywordProcessing()
        {
            dp = new DataProcessing();
            this.task = "";
            this.dateTypeKeywords = "";
            this.date = "";
            this.taskStatus = "";
        }

        public List<Command> processKeyword(string input)
        {
            input = input.ToLower();
            List<Command> result = new List<Command>();
            string firstWord = GetFirstWord(input);
            string crudTask = CheckCommonTask(firstWord);

            //no common command found, so try to add
            if (crudTask == GeneralConstant.NO_COMMAND)
            {
                    tryAddTask(input);
                    result = dp.processData(GeneralConstant.KEY_ADD, this.task, this.dateTypeKeywords, this.date);
                    return result;
            }
            else
            {
                string inputWithoutCommand = RemoveFirstWord(input).Trim();
                switch (crudTask)
                { 
                    case GeneralConstant.KEY_ADD:
                        tryAddTask(inputWithoutCommand);
                        return result = dp.processData(GeneralConstant.KEY_ADD, this.task, this.dateTypeKeywords, this.date);
                    case GeneralConstant.KEY_DELETE:
                        tryDeleteRetrieveTask(inputWithoutCommand);
                        return result = dp.processData(GeneralConstant.KEY_DELETE, this.task, this.dateTypeKeywords, this.date);
                    case GeneralConstant.KEY_UPDATE:
                        tryUpdateTask(inputWithoutCommand);
                        return result = dp.processData(GeneralConstant.KEY_UPDATE, this.task, this.dateTypeKeywords, this.date);
                    case GeneralConstant.KEY_RETRIEVE:
                        tryDeleteRetrieveTask(inputWithoutCommand);
                        return result = dp.processData(GeneralConstant.KEY_RETRIEVE, this.task, this.dateTypeKeywords, this.date);
                    case GeneralConstant.KEY_MARK:
                        tryMarkTask(inputWithoutCommand);
                        if (this.taskStatus != "")
                        {
                            dp = new DataProcessing(GeneralConstant.KEY_MARK, this.task, this.dateTypeKeywords, this.date, this.taskStatus);
                            return result = dp.processData(GeneralConstant.KEY_MARK, this.task, this.dateTypeKeywords, this.date);
                        }
                        break;
                    default:
                        break;
                }

                result.Add(new Command(GeneralConstant.INFO_DO_NOT_UNDERSTAND, GeneralConstant.ERROR, DateTime.MinValue, DateTime.MinValue, null, null));
                return result;
            }
        }

        //retrieve first word user typed to get user command, it could be command only or command with arguments
        //we have to just retrieve the first word which is done here
        private string GetFirstWord(string input)
        {
            try
            {
                //we only want one word hence word is 1
                int word = 1;
                for (int characters = 0; characters < input.Length; characters++)
                {
                    //increment until a space is spotted
                    if (input[characters] == ' ')
                        word--;
                    //one word spotted, hence return the string which is the command
                    if (word == GeneralConstant.FIRST_ITEM)
                        return input.Substring(GeneralConstant.FIRST_ITEM, characters);
                }
                return input;
            }
            catch (Exception)
            {
                //in the case where user typed nothing return empty which will be processed in the switch case in the startProcess method
                return string.Empty;
            }
        }

        //method to find if user typed in a common command such as add, delete, update
        private string CheckCommonTask(string firstWord)
        {
            if (isCreateTask(firstWord))
                return GeneralConstant.KEY_ADD;
            else if (isRetrieveTask(firstWord))
                return GeneralConstant.KEY_RETRIEVE;
            else if (isUpdateTask(firstWord))
                return GeneralConstant.KEY_UPDATE;
            else if (isDeleteTask(firstWord))
                return GeneralConstant.KEY_DELETE;
            else if (isMarkTask(firstWord))
                return GeneralConstant.KEY_MARK;
            else
                return GeneralConstant.NO_COMMAND;
        }

        private bool isCreateTask(string firstWord)
        {
            string[] possibleCreate = GeneralConstant.KEY_POSSIBLE_CREATE;

            for (int i = 0; i < possibleCreate.Length; i++)
            {
                if (firstWord.ToLower() == possibleCreate[i])
                    return true;
            }
            return false;
        }

        private bool isRetrieveTask(string firstWord)
        {
            string[] possibleRetrieve = GeneralConstant.KEY_POSSIBLE_RETRIEVE;

            for (int i = 0; i < possibleRetrieve.Length; i++)
            {
                if (firstWord.ToLower() == possibleRetrieve[i])
                    return true;
            }
            return false;
        }

        private bool isUpdateTask(string firstWord)
        {
            string[] possibleUpdate = GeneralConstant.KEY_POSSIBLE_UPDATE;

            for (int i = 0; i < possibleUpdate.Length; i++)
            {
                if (firstWord.ToLower() == possibleUpdate[i])
                    return true;
            }
            return false;
        }

        private bool isDeleteTask(string firstWord)
        {
            string[] possibleDelete = GeneralConstant.KEY_POSSIBLE_DELETE;

            for (int i = 0; i < possibleDelete.Length; i++)
            {
                if (firstWord.ToLower() == possibleDelete[i])
                    return true;
            }
            return false;
        }

        private bool isMarkTask(string firstWord)
        {
            string[] possibleMark = GeneralConstant.KEY_POSSIBLE_MARK;

            for (int i = 0; i < possibleMark.Length; i++)
            {
                if (firstWord.ToLower() == possibleMark[i])
                    return true;
            }
            return false;
        }

        private string RemoveFirstWord(string input)
        {
            try
            {
                //we only want one word hence word is 1
                int word = 1;
                for (int characters = 0; characters < input.Length; characters++)
                {
                    //increment until a space is spotted
                    if (input[characters] == ' ')
                        word--;
                    //one word spotted, hence return the string without the command
                    if (word == GeneralConstant.FIRST_ITEM)
                        return input.Substring(characters);
                }
                return input;
            }
            catch (Exception)
            {
                //in the case where user typed nothing return empty which will be processed in the switch case in the startProcess method
                return string.Empty;
            }
        }

        private void tryAddTask(string input)
        {
            List<string> taskPrepositionTime = CheckPrepositionOfTime(input);
            if (taskPrepositionTime.Count == 3) //means i have 3 values -> task, preposition and time
            {
                this.task = taskPrepositionTime[0];
                for (int i = 0; i < GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_A.Length; i++)
                {
                    if (taskPrepositionTime[1] == GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_A[i].ToString().Trim())
                    {
                        this.dateTypeKeywords = GeneralConstant.KEY_BEFORE;
                        break;
                    }
                    else
                        this.dateTypeKeywords = taskPrepositionTime[1];
                }
                this.date = taskPrepositionTime[2];
            }
            else //might be a case of floating so try add a floating
            {
                this.task = taskPrepositionTime[0];
                this.dateTypeKeywords = "";
                this.date = DateTime.MinValue.ToString();
            }
        }

        private void tryDeleteRetrieveTask(string input)
        {
            List<string> taskPrepositionTime = CheckPrepositionOfTime(input);
            if (taskPrepositionTime.Count == 3) //means i have 3 values -> task, preposition and time
            {
                this.task = taskPrepositionTime[0];
                this.dateTypeKeywords = taskPrepositionTime[1];
                this.date = taskPrepositionTime[2];
            }
            else //might be delete/retrieve all or delete/retrieve something only
            {
                this.task = taskPrepositionTime[0];
                this.dateTypeKeywords = "";
                this.date = DateTime.MinValue.ToString();
            }
        }

        private void tryUpdateTask(string input)
        {
            List<string> taskPrepositionTime = CheckPrepositionOfTime(input);
            if (taskPrepositionTime.Count == 3) //means i have 3 values -> task, preposition and time
            {
                this.task = taskPrepositionTime[0];
                this.dateTypeKeywords = taskPrepositionTime[1];
                this.date = taskPrepositionTime[2];
            }
            else //might be update 'taskA to taskB' etc
            {
                this.task = taskPrepositionTime[0];
                this.dateTypeKeywords = "";
                this.date = DateTime.MinValue.ToString();
            }
        }

        private void tryMarkTask(string input)
        {
            List<string> taskPrepositionTime = CheckPrepositionOfTime(input);
            if (taskPrepositionTime.Count == 3) //means i have 3 values -> task, preposition and time
            {
                this.task = taskPrepositionTime[0];
                this.dateTypeKeywords = taskPrepositionTime[1];
                List<string> dateAndStatus = CheckDateTaskAndStatus(taskPrepositionTime[2]); // further check if there is done and undone behind
                if (dateAndStatus.Count > 0)
                {
                    this.date = dateAndStatus[0];
                    this.taskStatus = dateAndStatus[1];
                }
            }
            else //left with task and done/undone
            {
                List<string> taskAndStatus = CheckDateTaskAndStatus(input);
                if (taskAndStatus.Count > 0)
                {
                    this.task = taskAndStatus[0];
                    this.taskStatus = taskAndStatus[1];
                }
            }
        }

        private List<string> CheckDateTaskAndStatus(string inputWithoutCommand)
        {
            List<string> dateAndStatus = new List<string>();

            string strSplitByTypeE;

            strSplitByTypeE = SplitByTypeE(inputWithoutCommand);

            if (strSplitByTypeE != string.Empty)
            {
                string[] splittedString = inputWithoutCommand.Split(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_E, StringSplitOptions.None);
                if (containDoneOrUndone(splittedString[1].ToString()))
                {
                    dateAndStatus.Add(splittedString[0].ToString());
                    dateAndStatus.Add(splittedString[1].ToString());
                }
                return dateAndStatus;
            }
            else
            {
                if (containDoneOrUndone(inputWithoutCommand))
                {
                    string doneOrUndone = checkDoneUndone(inputWithoutCommand);
                    dateAndStatus.Add(inputWithoutCommand.Replace(doneOrUndone, "").Trim());
                    dateAndStatus.Add(doneOrUndone);
                }
            }

            return dateAndStatus;
        }

        private string checkDoneUndone(string inputWithoutCommand)
        {
            if (inputWithoutCommand.Contains(GeneralConstant.TASK_UNDONE))
            {
                return GeneralConstant.TASK_UNDONE;
            }
            return GeneralConstant.TASK_DONE;
        }

        private bool containDoneOrUndone(string status)
        {
            for (int i = 0; i < GeneralConstant.KEY_POSSIBLE_TASK_STATUS.Length; i++)
            {
                if (status.Contains(GeneralConstant.KEY_POSSIBLE_TASK_STATUS[i].ToString()))
                    return true;
            }
            return false;
        }

        //method to check if user input can be split by various types prepositions of time
        //and decide the appropriate preposition to be passed down to DataProcessing later
        private List<string> CheckPrepositionOfTime(string inputWithoutCommand)
        {
            List<string> taskTime = new List<string>();
            string strSplitByTypeA, strSplitByTypeB, strSplitByTypeC, strSplitByTypeD;

            strSplitByTypeA = SplitByTypeA(inputWithoutCommand);
            strSplitByTypeB = SplitByTypeB(inputWithoutCommand);
            strSplitByTypeC = SplitByTypeC(inputWithoutCommand);
            strSplitByTypeD = SplitByTypeD(inputWithoutCommand);

            if (strSplitByTypeA != string.Empty)
            {
                string[] splittedString = inputWithoutCommand.Split(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_A, StringSplitOptions.None);
                taskTime.Add(splittedString[0].ToString());
                taskTime.Add(GeneralConstant.KEY_ON);
                taskTime.Add(splittedString[1].ToString());
                return taskTime;
            }
            if (strSplitByTypeB != string.Empty)
            {
                string[] splittedString = inputWithoutCommand.Split(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_B, StringSplitOptions.None);
                taskTime.Add(splittedString[0].ToString());
                taskTime.Add(GeneralConstant.KEY_BETWEEN);
                taskTime.Add(splittedString[1].ToString());
                return taskTime;
            }
            if (strSplitByTypeC != string.Empty)
            {
                string[] splittedString = inputWithoutCommand.Split(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_C, StringSplitOptions.None);
                taskTime.Add(splittedString[0].ToString());
                taskTime.Add(GeneralConstant.KEY_AFTER);
                taskTime.Add(splittedString[1].ToString());
                return taskTime;
            }
            if (strSplitByTypeD != string.Empty)
            {
                string[] splittedString = inputWithoutCommand.Split(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_D, StringSplitOptions.None);
                taskTime.Add(splittedString[1].ToString());
                taskTime.Add(GeneralConstant.KEY_FOR);
                taskTime.Add(splittedString[0].ToString());
                return taskTime;
            }
            taskTime.Add(inputWithoutCommand);
            return taskTime;
        }

        //see user input can be split by { " in ", " at ", " on ", " before "};
        private string SplitByTypeA(string inputWithoutCommand)
        {
            for (int i = 0; i < GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_A.Length; i++)
            {
                if (inputWithoutCommand.Contains(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_A[i].ToString()))
                    return GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_A[i];
            }
            return string.Empty;
        }

        //see user input can be split by { " between ", " from " };
        private string SplitByTypeB(string inputWithoutCommand)
        {
            for (int i = 0; i < GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_B.Length; i++)
            {
                if (inputWithoutCommand.Contains(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_B[i].ToString()))
                    return GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_B[i];
            }
            return string.Empty;
        }

        //see user input can be split by { " after " };
        private string SplitByTypeC(string inputWithoutCommand)
        {
            for (int i = 0; i < GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_C.Length; i++)
            {
                if (inputWithoutCommand.Contains(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_C[i].ToString()))
                    return GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_C[i];
            }
            return string.Empty;
        }

        //see user input can be split by { " for " };
        private string SplitByTypeD(string inputWithoutCommand)
        {
            for (int i = 0; i < GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_D.Length; i++)
            {
                if (inputWithoutCommand.Contains(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_D[i].ToString()))
                    return GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_D[i];
            }
            return string.Empty;
        }

        //see user input can be split by { " as " };
        private string SplitByTypeE(string inputWithoutCommand)
        {
            for (int i = 0; i < GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_E.Length; i++)
            {
                if (inputWithoutCommand.Contains(GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_E[i].ToString()))
                    return GeneralConstant.KEY_POSSIBLE_PREPOSITION_OF_TIME_E[i];
            }
            return string.Empty;
        }

    }
}
